/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.handlers;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.requests.CreateConnectionRequest;
import org.eclipse.gef.requests.ReconnectRequest;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Gate;
import org.eclipse.uml2.uml.InteractionUse;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.NamedElement;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.SeDiSubpartEditPart;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseOwnerCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.SelectPartCommand;
import com.cea.papyrus.diagram.sequence.sedi.editor.MessageSpecification;
import com.cea.papyrus.diagram.sequence.sedi.figures.AnchoringFigure;
import com.cea.papyrus.diagram.sequence.sedi.figures.SeDiAnchor;
import com.cea.papyrus.diagram.sequence.sedi.model.command.CreateGraphicalElementCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.CreateMessageCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.LifelineMessageEndPosition;
import com.cea.papyrus.diagram.sequence.sedi.model.command.MessageEndPosition;
import com.cea.papyrus.diagram.sequence.sedi.model.command.MoveMessageCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.diagram.SetAnchorHintsCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.diagram.UndoSetAnchorHintsCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * TODO Document class
 * 
 * @author frankd@ifi.uio.no
 */
public class MessageHandler implements LinkHandler {

    public final EditPolicy hostPolicy;

    public MessageHandler(final EditPolicy hostPolicy) {
        this.hostPolicy = hostPolicy;
    }

    @SuppressWarnings("unchecked")
    public Command[] getCreateCommands(CreateConnectionRequest request) {
        // SetAnchorHintCommand [SeDi]
        // Registers anchor hints that can be used when the
        // SeDi LinkElement is created. (This command is created
        // direcly by the EditPolicy.)
        // CreateMessageCommand [UML]
        // Creates the UML Message and adds it to the UML interaction.
        // When the command is executed, a GraphicalElement will be
        // created in response to the notification.
        // TODO Consider: Split the above command in three parts
        // 1. create MessageEnd -> senderEnd
        // 2. create MessageEnd -> ReceiverEnd
        // 3. create Message(senderEnd, receiverEnd)
        // This will make it easier to handle message end movement later
        final EditPart rootEditPart = this.hostPolicy.getHost().getRoot();
        MessageSpecification messageSpec = (MessageSpecification) request.getNewObject();

        final SeDiSubpartEditPart senderEP = (SeDiSubpartEditPart) request.getSourceEditPart();
        final GraphNode senderView = (GraphNode) senderEP.getModel();
        final ConnectionAnchor sendAnchor =
                (ConnectionAnchor) request.getExtendedData().get("SourceAnchor");

        final SeDiSubpartEditPart receiverEP = (SeDiSubpartEditPart) request.getTargetEditPart();
        final GraphElement receiverView = (GraphElement) receiverEP.getModel();
        final ConnectionAnchor receiveAnchor = receiverEP.getTargetConnectionAnchor(request);

        final Diagram diagram = LookForElement.getDiagram(senderView);

        final AnalyseOwnerCommand sendOwnerCmd = new AnalyseOwnerCommand();
        sendOwnerCmd.setDiagram(diagram);
        sendOwnerCmd.setLocation(sendAnchor.getLocation(null));
        sendOwnerCmd.setAnyEditPart(rootEditPart);

        final AnalyseOwnerCommand receiveOwnerCmd = new AnalyseOwnerCommand();
        receiveOwnerCmd.setDiagram(diagram);
        receiveOwnerCmd.setLocation(receiveAnchor.getLocation(null));
        receiveOwnerCmd.setAnyEditPart(rootEditPart);

        final CreateMessageCommand createCmd = new CreateMessageCommand();
        createCmd.setMessageSort(messageSpec.getMessageSort());

        final CreateGraphicalElementCommand createGECmd = new CreateGraphicalElementCommand();
        createGECmd.setAnyEditPart(rootEditPart);

        final SelectPartCommand selectCmd = new SelectPartCommand();
        selectCmd.setAnyEditPart(rootEditPart.getRoot());
        selectCmd.setSelectForEdit(true);

        Command glue1 = new Command() {
            public void execute() {
                createCmd.setSendPosition(senderEP.getMessagePosition(sendAnchor, true,
                        sendOwnerCmd.getOwner2()));
                createCmd.setReceivePosition(receiverEP.getMessagePosition(receiveAnchor, true,
                        receiveOwnerCmd.getOwner2()));
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                // nothing to do.
            }
        };

        Command glue2 = new Command() {
            public void execute() {
                selectCmd.setTarget(createCmd.getMessage());
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                this.execute();
            }
        };

        Command glueGE = new Command() {
            @Override
            public void execute() {
                createGECmd.setParentGE(diagram);
                createGECmd.setElement(createCmd.getMessage());
            }
        };

        Command setPosition = new Command() {
            @Override
            public void execute() {
                GraphEdge edge = ModelUtil.getGraphEdge(createCmd.getMessage(), diagram);
                edge.getAnchor().get(0).setPosition(
                        new Point(((SeDiAnchor) sendAnchor).getOffsetX(), ((SeDiAnchor) sendAnchor)
                                .getOffsetY()));
                edge.getAnchor().get(1).setPosition(
                        new Point(((SeDiAnchor) receiveAnchor).getOffsetX(),
                                ((SeDiAnchor) receiveAnchor).getOffsetY()));
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                this.execute();
            }
        };

        return new Command[] {
                sendOwnerCmd, receiveOwnerCmd, glue1, createCmd, glueGE, createGECmd, glue2,
                selectCmd, setPosition
        };
    }

    @SuppressWarnings("unchecked")
    public Command[] getDeleteCommands(Request request) {
        final GraphEdge view = (GraphEdge) hostPolicy.getHost().getModel();
        if (view.getContainer() != null) {
            Uml1SemanticModelBridge uml1SemanticModelBridge =
                    (Uml1SemanticModelBridge) view.getSemanticModel();
            Element target = uml1SemanticModelBridge.getElement();
            if (target instanceof Message) {
                target = ((Message) target).getInteraction();
            }

//            final DeleteGraphicalLinkCommand viewCmd = new DeleteGraphicalLinkCommand();
//            viewCmd.setDiagram(LookForElement.getDiagram(view));
//            viewCmd.setLink(ModelUtil.<NamedElement> getSemantic(view));

            final SetAnchorHintsCommand hintCmd = new SetAnchorHintsCommand();
            hintCmd.setSourceHint(ModelUtil.getSourceRef(view));
            hintCmd.setTargetHint(ModelUtil.getTargetRef(view));

            final SelectPartCommand selectPartCommand = new SelectPartCommand();
            selectPartCommand.setAnyEditPart(hostPolicy.getHost().getRoot());
            selectPartCommand.setSelectForEdit(false);
            selectPartCommand.setTarget(target);

//            return new Command[] {
//                    viewCmd, new DeleteMessageCommand(ModelUtil.<Message> getSemantic(view)),
//                    selectPartCommand, viewCmd.getHintCommand()
//            };
            return new Command[] {
                    selectPartCommand
            };
        }
        return new Command[] {};
    }

    @SuppressWarnings("unchecked")
    public Command[] getReconnectSourceCommands(final ReconnectRequest request) {
        List<Command> cmd = new ArrayList<Command>(9);

        final ConnectionEditPart connectionEP = request.getConnectionEditPart();
        final GraphEdge link = (GraphEdge) connectionEP.getModel();

        final SeDiSubpartEditPart newSourceEP = (SeDiSubpartEditPart) request.getTarget();
        // final GraphicalElement newSourceModel = (GraphicalElement)
        // newSourceEP.getModel();
        final SeDiAnchor newSourceAnchor =
                (SeDiAnchor) newSourceEP.getSourceConnectionAnchor(request);

        if (!newSourceAnchor.acceptReconnectSource(request))
            return null;

        final AnalyseOwnerCommand sendOwnerCmd = new AnalyseOwnerCommand();
        sendOwnerCmd.setDiagram(LookForElement.getDiagram(link));
        sendOwnerCmd.setLocation(newSourceAnchor.getLocation(null));
        sendOwnerCmd.setAnyEditPart(newSourceEP);

        final MoveMessageCommand moveSourceCmd = new MoveMessageCommand();
        moveSourceCmd.setMessage(ModelUtil.<Message> getSemantic(link));

        Command glue1 = new Command() {
            public void execute() {
                moveSourceCmd.setSendPosition(findSourcePosition(newSourceEP, link,
                        newSourceAnchor, sendOwnerCmd.getOwner2()));
            }
        };

        cmd.add(sendOwnerCmd);
        cmd.add(glue1);
        cmd.add(moveSourceCmd);

        // Ensure that proper hints are available to support undo
        UndoSetAnchorHintsCommand undoHints = new UndoSetAnchorHintsCommand();
        undoHints.setSourceHint(ModelUtil.getSourceRef(link));
        undoHints.setTargetHint(ModelUtil.getTargetRef(link));
        cmd.add(undoHints);

        // Handle movement of receive end
        Message message = ModelUtil.<Message> getSemantic(link);
        final SeDiSubpartEditPart targetEP = (SeDiSubpartEditPart) connectionEP.getTarget();
        final AnchoringFigure targetFigure = (AnchoringFigure) targetEP.getFigure();
        final String oldTargetRef = ModelUtil.getTargetRef(link);
        final String oldSourceRef = ModelUtil.getSourceRef(link);
        final SeDiSubpartEditPart oldSourceEP = (SeDiSubpartEditPart) connectionEP.getSource();
        oldSourceEP.getConnectionAnchor(oldSourceRef);

        final Point oldSourceLocation = link.getAnchor().get(0).getPosition().getCopy();
        final Point newSourceLocation = newSourceAnchor.getLocation(null);
        final int distance = newSourceLocation.y - oldSourceLocation.y;
        final String newTargetRef =
                targetFigure.getBestTargetAnchor(ModelUtil.getTargetRef(link), distance,
                        newSourceLocation.x);
        final SeDiAnchor newTargetAnchor = (SeDiAnchor) targetEP.getConnectionAnchor(newTargetRef);
        if (moveTargetEnd(message)) {
            if (newTargetRef != oldTargetRef) {
                // Test that the new anchor is acceptable
                ReconnectRequest moveTargetRequest = new ReconnectRequest();
                moveTargetRequest.setConnectionEditPart(connectionEP);
                moveTargetRequest.setTargetEditPart(targetEP);
                moveTargetRequest.setType(RequestConstants.REQ_RECONNECT_TARGET);
                moveTargetRequest.setLocation(newTargetAnchor.getLocation(null));

                Command moveTargetCmd = targetEP.getCommand(moveTargetRequest);
                if (moveTargetCmd != null) { // request was accepted
                    final SetAnchorHintsCommand hintCmd = new SetAnchorHintsCommand();
                    hintCmd.setTargetHint(newTargetRef);
                    cmd.add(hintCmd);
                    cmd.add(moveTargetCmd);
                }
            }
        }

        Command setPosition = new Command() {
            @Override
            public void undo() {
                link.getAnchor().get(0).setPosition(oldSourceLocation);
            }

            @Override
            public void redo() {
                execute();
            }

            @Override
            public void execute() {
                link.getAnchor().get(0).setPosition(
                        new Point(newSourceAnchor.getOffsetX(), newSourceAnchor.getOffsetY()));
            }
        };
        cmd.add(setPosition);

        Command[] cmds = new Command[cmd.size()];
        return cmd.toArray(cmds);
    }

    private boolean moveTargetEnd(final Message message) {
        // don't move the receiving end when it is a port on
        // the interaction frame or a CF
        if (!(message.getReceiveEvent() instanceof Gate))
            return true;
        final Gate gate = (Gate) message.getReceiveEvent();
        final Element gateOwner = gate.getOwner();
        return gateOwner instanceof InteractionUse;
    }

    @SuppressWarnings("unchecked")
    public Command[] getReconnectTargetCommands(ReconnectRequest request) {
        final GraphEdge link = (GraphEdge) request.getConnectionEditPart().getModel();

        final SeDiSubpartEditPart receiverEP = (SeDiSubpartEditPart) request.getTarget();
        final ConnectionAnchor receiveAnchor = receiverEP.getTargetConnectionAnchor(request);
        final Point oldPosition = link.getAnchor().get(1).getPosition().getCopy();

        final AnalyseOwnerCommand receiveOwnerCmd = new AnalyseOwnerCommand();
        receiveOwnerCmd.setDiagram(LookForElement.getDiagram(link));
        receiveOwnerCmd.setLocation(receiveAnchor.getLocation(null));
        receiveOwnerCmd.setAnyEditPart(receiverEP);

        final MoveMessageCommand moveTargetCmd = new MoveMessageCommand();
        moveTargetCmd.setMessage(ModelUtil.<Message> getSemantic(link));

        Command glue2 = new Command() {
            public void execute() {
                moveTargetCmd.setReceivePosition(findTargetPosition(receiverEP, link,
                        receiveAnchor, receiveOwnerCmd.getOwner2()));
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                // does nothing.
            }
        };

        // final MoveMessageCommand cmd = new MoveMessageCommand();
        // cmd.setMessage(link.getTypedElement());
        // cmd.setReceiver(((GraphicalElement)receiverEP.getModel()).getNamedElement());
        // cmd.setReceiverPosition(findTargetPosition(receiverEP, link,
        // receiveAnchor));

        UndoSetAnchorHintsCommand undoHints = new UndoSetAnchorHintsCommand();
        undoHints.setSourceHint(ModelUtil.getSourceRef(link));
        undoHints.setTargetHint(ModelUtil.getTargetRef(link));

        Command setPosition = new Command() {
            /**
             * @see org.eclipse.gef.commands.Command#execute()
             */
            @Override
            public void execute() {
                SeDiAnchor seDiAnchor = (SeDiAnchor) receiveAnchor;
                link.getAnchor().get(1).setPosition(
                        new Point(seDiAnchor.getOffsetX(), seDiAnchor.getOffsetY()));
            }

            /**
             * @see org.eclipse.gef.commands.Command#undo()
             */
            @Override
            public void undo() {
                link.getAnchor().get(1).setPosition(oldPosition);
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                this.execute();
            }
        };

        // return new Command[] { cmd };
        return new Command[] {
                receiveOwnerCmd, glue2, moveTargetCmd, undoHints, setPosition
        };
    }

    // --- Helper methods ---

    // TODO This is very messy. Clean up

    private MessageEndPosition findSourcePosition(
            final SeDiSubpartEditPart<? extends NamedElement> editPart, final GraphEdge link,
            final ConnectionAnchor sourceAnchor, final NamedElement owner) {
        MessageEndPosition position = editPart.getMessagePosition(sourceAnchor, true, owner);
        if (position instanceof LifelineMessageEndPosition
                && ((LifelineMessageEndPosition) position).getLifelinePosition() == ModelUtil
                        .<Message> getSemantic(link).getSendEvent()) {
            // newEP == oldEP since can only happen when moving within the same
            // EP
            ConnectionAnchor oldSourceAnchor =
                    editPart.getConnectionAnchor(ModelUtil.getSourceRef(link));
            position = editPart.getMessagePosition(oldSourceAnchor, true, owner);
        }
        return position;
    }

    private MessageEndPosition findTargetPosition(
            final SeDiSubpartEditPart<? extends NamedElement> editPart, final GraphEdge link,
            final ConnectionAnchor targetAnchor, final NamedElement owner) {
        MessageEndPosition position = editPart.getMessagePosition(targetAnchor, false, owner);
        if (position instanceof LifelineMessageEndPosition
                && ((LifelineMessageEndPosition) position).getLifelinePosition() == ModelUtil
                        .<Message> getSemantic(link).getReceiveEvent()) {
            // newEP == oldEP since can only happen when moving within the same
            // EP
            ConnectionAnchor oldTargetAnchor =
                    editPart.getConnectionAnchor(ModelUtil.getTargetRef(link));
            position = editPart.getMessagePosition(oldTargetAnchor, false, owner);
        }
        return position;
    }
}
